package algo_annotations.heuristique;

import algo_annotations.data.Data_a;
import algo_annotations.data.annotations.GroupeAnno;
import algo_annotations.data.sites.Site;
import algo_annotations.data.sites.SousGroupeSite;
import algo_annotations.params.Params_a;
import commons.params_appli.ParamsAppli;
import commons.logs.Logger;
import commons.utils.UtilsCalcul;
import commons.utils.UtilsTime;
import java.util.ArrayList;

public class AlgoGenetic {

	private Data_a data;
	private Params_a params;
	
	// Nb de CPU pour le calcul
	private int nbCPU=0;

	public ArrayList<TraitementSousGroupe> vAllTrt_pass1;
	public ArrayList<TraitementSousGroupe> vAllTrt_pass2;
	public TraitementSousGroupe currentTrt;

	// Le meilleure groupe d'annotation, issu des meilleurs genomes des differents sous groupes
	private GroupeAnno bestGroupeAnno;
	public GroupeAnno getBestGroupeAnno() {return bestGroupeAnno;}
	public double bestNoteGlobaleCourante = 0;

	// un groupe d'anno par thread/CPU pour les calculs de notes
	private GroupeAnno[] tabGroupeAnnos;
	public GroupeAnno getGroupeAnno(int numCPU) {return tabGroupeAnnos[numCPU];}


	public AlgoGenetic(Data_a data) {
		this.data = data;
		this.params = data.params;
		this.nbCPU = ParamsAppli.getParam_i("Autogene.NB_CPU");
		currentTrt = null;
//		createThread();
	}

	// aide le gc (utilisé en mode interactif)
	// la memoire est occupée par le currentTraitement, car c'est le seul qui a un vivier rempli
	public void dispose() {
		if (currentTrt != null)
			currentTrt.dispose();
		currentTrt = null;

	}


	// A lancer avant tout nouveau traitement
	public void initAlgo() {
		Logger.log_INFO1("********************************************");
		Logger.log_INFO1("INITIALISATION DE L'ALGORITHME GENETIQUE");
		Logger.log_INFO1(" -> nb CPU utilisés = " + nbCPU);
		Logger.log_INFO1("");
		
		// Un groupe d'anno par CPU, pour éviter tout souci
		tabGroupeAnnos = new GroupeAnno[nbCPU];
		for (int i=0; i<nbCPU ; i++) {
			GroupeAnno ga = new GroupeAnno(data.grpSite, params);
			tabGroupeAnnos[i] = ga;
		}

		// Meilleur groupe d'anno
		bestGroupeAnno = new GroupeAnno(data.grpSite, params);

		// S'il y a eu un découpage du problème,
		//  on l'alimente d'annotations déterminées comme pre-placée avant le début de tout traitement
		if (ParamsAppli.getParam_b("Anno.UTILISE_DECOUPAGE_DONNEE_PASSE_1")) {

			// Pré-placement des annos dans le bestGroupeAnno
			SousGroupeSite sgs;
			// d'abord on place les annos pré-placées parfaitement
			sgs = data.grpSite.getSousGroupeSite_pass1(-1);
			for (int index : sgs.vIndexSite) {
				Site site = data.grpSite.vSite.get(index);
				bestGroupeAnno.vAnno.get(index).setPosition(site.geneX_prePlacementIndependant, site.geneY_prePlacementIndependant, site.geneML_prePlacementIndependant);
			}
			// puis les annos presque bien placées
			sgs = data.grpSite.getSousGroupeSite_pass1(0);
			for (int index : sgs.vIndexSite) {
				Site site = data.grpSite.vSite.get(index);
				bestGroupeAnno.vAnno.get(index).setPosition(site.geneX_prePlacementIndependant, site.geneY_prePlacementIndependant, site.geneML_prePlacementIndependant);
			}
		}
		bestGroupeAnno.computeNote();

		vAllTrt_pass1 = new ArrayList<TraitementSousGroupe>();
		vAllTrt_pass2 = new ArrayList<TraitementSousGroupe>();
	}
	
	// Cree des Traitement 1ère passe à partir des sous-groupe de passe 1 deja calculé dans le GrpSite
	public void createTraitements_pass1() {
		// Préparation de la liste des Traitement en vue du calcul Automatique
		vAllTrt_pass1 = new ArrayList<TraitementSousGroupe>();

		// On commence par les sous groupe-connexes
		for (int i=1 ; i<=data.grpSite.getNbSousGroupeConnexe_pass1() ; i++) {
			SousGroupeSite sgs = data.grpSite.getSousGroupeSite_pass1(i);
			TraitementSousGroupe trt = createTraitementSousGroupeSite(sgs);
			if (trt != null)
				vAllTrt_pass1.add(trt);
		}

		// Puis quand tout le monde est bien placé, on re-tente un placement
		//  des annos ALMOST-BEST : en général, il y a de la place pour qu'elles
		//  trouvent leur quasi-BEST emplacement
		SousGroupeSite sgs = data.grpSite.getSousGroupeSite_pass1(0);
		TraitementSousGroupe trt = createTraitementSousGroupeSite(sgs);
		if (trt != null)
			vAllTrt_pass1.add(trt);
	}


	// Cree des Traitement 2eme passe à partir des sous-groupe de passe 2 deja calculé dans le GrpSite
	// peut etre appelé plusieurs fois en mode interactif : on nettoie bien en debut de traitement
	public void createTraitements_pass2(int tailleVivier, int nbIter) {
		if (vAllTrt_pass2 == null) vAllTrt_pass2 = new ArrayList<TraitementSousGroupe>();
		vAllTrt_pass2.clear();
		for (SousGroupeSite sgs : data.grpSite.v_Sg_pass2) {
			TraitementSousGroupe trt = createTraitementSousGroupeSite(sgs, tailleVivier, nbIter);
			vAllTrt_pass2.add(trt);
		}
	}



	// public car peut etre appelé par le Data en mode interactif
	public TraitementSousGroupe createTraitementSousGroupeSite(SousGroupeSite sgs) {
		if (sgs==null) return null; // si pas de sous-decoupage probleme, certains sous-groupes sont nulls
		if (sgs.vIndexSite.isEmpty()) return null;

		int nbIter = params.algo_nbIter_pass1;
		int tailleVivier = params.algo_tailleVivier_pass1;

		return createTraitementSousGroupeSite (sgs, tailleVivier, nbIter);
	}

	
	public TraitementSousGroupe createTraitementSousGroupeSite(SousGroupeSite sgs, int tailleVivier, int nbIter) {
		if (sgs==null) return null; // si pas de sous-decoupage probleme, certains sous-groupe sont nulls
		if (sgs.vIndexSite.isEmpty()) return null;

		TraitementSousGroupe trt = new TraitementSousGroupe();
		trt.sgs = sgs;

		trt.sgs = sgs;
		trt.tailleVivier = tailleVivier;
		trt.nbIter = nbIter;
		trt.numIter = 0;

		// Paramètres d'évolution du vivier
		trt.evolMUTATION = new Evolution();
		trt.evolCROSSOVER = new Evolution();
		trt.evolTAKEBEST = new Evolution();
		trt.evolADJUST = new Evolution();

		//*** C2 ***************************************************************
//		int dureeMutation = nbIter/3;
//		trt.evolMUTATION.setProfilPoids_decroissSimple(80.0, 10.0, dureeMutation);
//		trt.evolMUTATION.setProfilParam_decroissSimple(1.0, 0.05, dureeMutation*2);
//
//		int dureeCrossOver = nbIter/3;
//		trt.evolCROSSOVER.setProfilPoids_decroissSimple(20.0, 5.0, dureeCrossOver);
//		trt.evolCROSSOVER.setProfilParam_decroissSimple(0.2, 0.02, dureeCrossOver*2);
//
//		int iterSartTakeBest = nbIter/4;
//		trt.evolTAKEBEST.setProfilPoids_croissDifferee(50.0, iterSartTakeBest/2, iterSartTakeBest);
//		trt.evolTAKEBEST.setProfilParam_cte(0.2);
//
//		int iterSartAdjust = nbIter/4;
//		trt.evolADJUST.setProfilPoids_croissDifferee(50.0, iterSartAdjust/2, iterSartAdjust);
//		trt.evolADJUST.setProfilParam_croissDifferee(0.5, iterSartAdjust/2, 2*iterSartAdjust);
		//**********************************************************************

		//*** C5 ***************************************************************
//		int dureeMutation = nbIter/2;
//		trt.evolMUTATION.setProfilPoids_decroissSimple(60.0, 10.0, dureeMutation);
//		trt.evolMUTATION.setProfilParam_decroissSimple(1.0, 0.05, dureeMutation*2);
//
//		int dureeCrossOver = nbIter/2;
//		trt.evolCROSSOVER.setProfilPoids_decroissSimple(40.0, 5.0, dureeCrossOver);
//		trt.evolCROSSOVER.setProfilParam_decroissSimple(0.2, 0.02, dureeCrossOver*2);
//
//		int iterSartTakeBest = nbIter/3;
//		trt.evolTAKEBEST.setProfilPoids_croissDifferee(50.0, iterSartTakeBest/2, iterSartTakeBest);
//		trt.evolTAKEBEST.setProfilParam_croissDifferee(0.5, iterSartTakeBest/2, 2*iterSartTakeBest);
//
//		int iterSartAdjust = nbIter/3;
//		trt.evolADJUST.setProfilPoids_croissDifferee(50.0, iterSartAdjust/2, iterSartAdjust);
//		trt.evolADJUST.setProfilParam_croissDifferee(0.7, iterSartAdjust/2, 2*iterSartAdjust);
		//**********************************************************************

		//*** C'5 ***************************************************************
		// decroissance + rapide du param mutation et crossover
//		int dureeMutation = nbIter/2;
//		trt.evolMUTATION.setProfilPoids_decroissSimple(60.0, 10.0, dureeMutation);
//		trt.evolMUTATION.setProfilParam_decroissSimple(1.0, 0.05, dureeMutation);
//
//		int dureeCrossOver = nbIter/2;
//		trt.evolCROSSOVER.setProfilPoids_decroissSimple(40.0, 5.0, dureeCrossOver);
//		trt.evolCROSSOVER.setProfilParam_decroissSimple(0.2, 0.02, dureeCrossOver);
//
//		int iterSartTakeBest = nbIter/3;
//		trt.evolTAKEBEST.setProfilPoids_croissDifferee(50.0, iterSartTakeBest/2, iterSartTakeBest);
//		trt.evolTAKEBEST.setProfilParam_croissDifferee(0.5, iterSartTakeBest/2, 2*iterSartTakeBest);
//
//		int iterSartAdjust = nbIter/3;
//		trt.evolADJUST.setProfilPoids_croissDifferee(50.0, iterSartAdjust/2, iterSartAdjust);
//		trt.evolADJUST.setProfilParam_croissDifferee(0.7, iterSartAdjust/2, 2*iterSartAdjust);
		//**********************************************************************

		//*** C6 ***************************************************************
		// % % final de cross over -> 2.5 au lieu de 5
//		int dureeMutation = nbIter/2; // /4 et /3 : moins bon que /2
//		trt.evolMUTATION.setProfilPoids_decroissSimple(60.0, 10.0, dureeMutation); // valeur finale 20 et 5 moins bon que 10
//		trt.evolMUTATION.setProfilParam_decroissSimple(1.0, 0.05, dureeMutation);
//
//		int dureeCrossOver = nbIter/2; // /4 et /3 : moins bon que /2
//		trt.evolCROSSOVER.setProfilPoids_decroissSimple(40.0, 2.5, dureeCrossOver); // valeur finale 10 moins bon que 5, 2.5 mieux que 5
//		trt.evolCROSSOVER.setProfilParam_decroissSimple(0.2, 0.02, dureeCrossOver);
//
//		int iterSartTakeBest = nbIter/3;
//		trt.evolTAKEBEST.setProfilPoids_croissDifferee(50.0, iterSartTakeBest/2, iterSartTakeBest);
//		trt.evolTAKEBEST.setProfilParam_croissDifferee(0.5, iterSartTakeBest/2, 2*iterSartTakeBest);
//
//		int iterSartAdjust = nbIter/3;
//		trt.evolADJUST.setProfilPoids_croissDifferee(50.0, iterSartAdjust/2, iterSartAdjust);
//		trt.evolADJUST.setProfilParam_croissDifferee(0.7, iterSartAdjust/2, 2*iterSartAdjust);
		//**********************************************************************

		//*** C'6 ***************************************************************
		// param adjust : 0.9/0.7
		int dureeMutation = nbIter/2; // /4 et /3 : moins bon que /2
		trt.evolMUTATION.setProfilPoids_decroissSimple(60.0, 10.0, dureeMutation); // valeur finale 20 et 5 moins bon que 10
		trt.evolMUTATION.setProfilParam_decroissSimple(1.0, 0.05, dureeMutation);

		int dureeCrossOver = nbIter/2; // /4 et /3 : moins bon que /2
		trt.evolCROSSOVER.setProfilPoids_decroissSimple(40.0, 2.5, dureeCrossOver); // valeur finale 10 moins bon que 5, 2.5 mieux que 5
		trt.evolCROSSOVER.setProfilParam_decroissSimple(0.2, 0.02, dureeCrossOver);

		int iterSartTakeBest = nbIter/4; // /4 semble le mieux (mieux que /2 ou /5)
		trt.evolTAKEBEST.setProfilPoids_croissDifferee(50.0, iterSartTakeBest/2, iterSartTakeBest); // 30 moins bon que 50, 70 bof, 60 bof
		trt.evolTAKEBEST.setProfilParam_croissDifferee(0.6, iterSartTakeBest/2, 2*iterSartTakeBest); // 0.6 mieux que 0.5 _ 0.3 BCP moins bon que 0.5

		int iterSartAdjust = nbIter/3;
		trt.evolADJUST.setProfilPoids_croissDifferee(50.0, iterSartAdjust/2, iterSartAdjust); //100,70,40 -bon  que 50
		trt.evolADJUST.setProfilParam_croissDifferee(0.8, iterSartAdjust/2, 2*iterSartAdjust); // / 0.7
		//**********************************************************************
		return trt;
	}


	// Demarre un nouveau traitement de sous groupe
	public void startTraitementSousGroupe (TraitementSousGroupe trt) {
		currentTrt = trt;
		trt.vivier = new Vivier(trt.sgs, trt.tailleVivier, params.bUseMultiligne);

		// Avant le début du traitement de ce Sousgroupe,
		//  on préalimente les GroupeAnno de chaque CPU avec la position des annos déjà bien placées
		//  du best groupe Anno
		for (int i=0; i<nbCPU ; i++)
			tabGroupeAnnos[i].becomeCopyOf(bestGroupeAnno);

		Logger.log_INFO1("\n*********************************************************");
		Logger.log_INFO1("** Traitement de : " + trt.sgs + "**");
		Logger.log_INFO1("Nb. annotations = " + trt.sgs.size());
		Logger.log_INFO1("Paramètres de traitement :");
		Logger.log_INFO1(" - taille vivier = " + trt.tailleVivier);
		Logger.log_INFO1(" - nb iter total = " + trt.nbIter);
	}

	// Libère la mémoire occupée par le TraitementSousGroupe courant, notamment son vivier
	//  pour le traitement global automatique,, ne pas libérer cette mémoier peut conduire
	//  à une exception OutOfMemory
	// Affiche une stat en fin de traitement du sous groupe courant
	public void finTraitementSousGroupe () {
		currentTrt.currentNote = UtilsCalcul.round(currentTrt.vivier.getNoteBestGenome(), 4);
		bestNoteGlobaleCourante = UtilsCalcul.round(bestGroupeAnno.computeNote(), 4);
		
		Logger.log_INFO1("=> Nb iter réalisées : " + currentTrt.numIter);
		Logger.log_INFO1("=> Note finale : " + currentTrt.currentNote + "   note TOTALE : " + bestNoteGlobaleCourante);
		Logger.log_INFO1("*********************************************************");

		currentTrt.dispose();
		currentTrt = null;
	}


	// step du Traitement en cours en cours
	public void step() {
		if (currentTrt==null || currentTrt.evolADJUST==null) {
			Logger.log_WARN("Demande de calcul mais aucun Traitement de démarré !");
			return;
		}
		
		Logger.log_DEBUG1("------------------------------------------");

		UtilsTime.startTimer("modif vivier");
		currentTrt.vivier.modifyVivier(currentTrt.numIter, currentTrt.evolMUTATION, currentTrt.evolCROSSOVER, currentTrt.evolTAKEBEST, currentTrt.evolADJUST);
		UtilsTime.stopTimer("modif vivier");

		UtilsTime.startTimer("calcul notes");
	    computeNotes();
		UtilsTime.stopTimer("calcul notes");

		// rapide donc on ne chronometre pas
		currentTrt.vivier.sortGenomes();

		currentTrt.numIter++;

		// a la fin du step, on met a jour le bestGroupeAnno
		Genome bestGenomeForCurrentSsGroupeSite = currentTrt.vivier.getBestGenome();
		bestGroupeAnno.updateFromGenome(currentTrt.sgs, bestGenomeForCurrentSsGroupeSite);

		currentTrt.currentNote = UtilsCalcul.round(currentTrt.vivier.getNoteBestGenome(), 4);
		bestNoteGlobaleCourante = UtilsCalcul.round(bestGroupeAnno.computeNote(), 4);

		Logger.log_INFO2("=> Iter. n° : " + currentTrt.numIter + "   note ss-gpe = " + currentTrt.currentNote + "   note TOTALE = " + bestNoteGlobaleCourante);
		Logger.log_DEBUG1(UtilsTime.getStringDuree("modif vivier"));
		Logger.log_DEBUG1(UtilsTime.getStringDuree("calcul notes"));
	}


//	private ComputeNoteThread[] tabThread = null;
//	private Semaphore[] tabSemaphore;
//
//	private void createThread() {
//		tabSemaphore = new Semaphore[ParamsAppli.NB_CPU];
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			tabSemaphore[i] = new Semaphore(1);
//		// on prend tous les jeton pour que les thread démarrés soient immédiatement en pause
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			try {tabSemaphore[i].acquire();} catch (InterruptedException ex) {}
//
//		tabThread = new ComputeNoteThread[ParamsAppli.NB_CPU];
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			tabThread[i] = new ComputeNoteThread(i);
//
//		// on lance tous les thread qui se mettent immédiatement en pause
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			tabThread[i].start();
//	}
//
//	private void computeNotes() {
//
//		// index debut et fin à traiter dans le vivier pour un thread donné
//		int indexDeb, indexFin=-1;
//		int nbGenomePerThread = currentTrt.vivier.getTailleVivier()/ParamsAppli.NB_CPU;
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++) {
//			indexDeb = indexFin+1;
//			indexFin += nbGenomePerThread;
//			// on force la borne de fin pour le dernier thread (cf. erreurs d'arrondis)
//			if (i==ParamsAppli.NB_CPU-1) indexFin = currentTrt.vivier.getTailleVivier()-1;
//
//			// on donne du travail a chaque thread en précisant les bornes de travail
//			tabThread[i].giveJob(indexDeb, indexFin);
//		}
//
//		// on réveille tous les thread, en libérant les jetons
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			tabSemaphore[i].release();
//
//
//		// Une fois tous les thread lancés, on attend qu'ils aient fini
//		//  pour cela, on tente de récupérer les défférents jetons utilisés à chaque
//		//  réveil de thread, jeton rendu par chacun en fin de traitement
//		for (int i=0 ; i<ParamsAppli.NB_CPU ; i++)
//			try {tabSemaphore[i].acquire();} catch (InterruptedException ex) {}
//
//
//	}
//
//
//	private class ComputeNoteThread extends Thread {
//		private int indexDeb, indexFin, numCPU;
//		public ComputeNoteThread(int numCPU) {
//			this.indexDeb = -1;
//			this.indexFin = -1;
//			this.numCPU = numCPU;
//		}
//		public void giveJob(int indexDeb, int indexFin) {
//			this.indexDeb = indexDeb;
//			this.indexFin = indexFin;
//		}
//
//		@Override
//		public void run() {
//			while(true) {
//
//				// au départ, on est en pause
//				try {tabSemaphore[numCPU].acquire();} catch (InterruptedException ex) {}
//
//				// le thread principal nous a réveillé, on lance un calcul
//				currentTrt.vivier.computeNotes(tabGroupeAnnos[numCPU], indexDeb, indexFin);
//				// en fin de calcul, on rend le jeton
//				tabSemaphore[numCPU].release();
//
//			}
//		}
//	}


	private void computeNotes() {
		ComputeNoteThread tabThread[] = new ComputeNoteThread[nbCPU];
		// index debut et fin à traiter dans le vivier pour un thread donné
		int indexDeb, indexFin=-1;
		int nbGenomePerThread = currentTrt.vivier.getTailleVivier()/nbCPU;
		for (int i=0 ; i<nbCPU ; i++) {
			indexDeb = indexFin+1;
			indexFin += nbGenomePerThread;
			// on force la borne de fin pour le dernier thread (cf. erreurs d'arrondis)
			if (i==nbCPU-1) indexFin = currentTrt.vivier.getTailleVivier()-1;
			tabThread[i] = new ComputeNoteThread(indexDeb, indexFin, i);
		}
		// On lance tous les thread
		for (int i=0 ; i<nbCPU ; i++)
			tabThread[i].start();
		// Puis on attend qu'ils aient tous terminé
		try {
		for (int i=0 ; i<nbCPU ; i++)
			tabThread[i].join();
		} catch (InterruptedException ex) {}
	}

	private class ComputeNoteThread extends Thread {
		private int indexDeb, indexFin, numCPU;
		public ComputeNoteThread(int indexDeb, int indexFin, int numCPU) {
			this.indexDeb = indexDeb;
			this.indexFin = indexFin;
			this.numCPU = numCPU;
		}
		@Override
		public void run() {
			currentTrt.vivier.computeNotes(tabGroupeAnnos[numCPU], indexDeb, indexFin);
		}
	}

}
